package se.webbzon.boltzmann.editor.view;

import java.awt.BorderLayout;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.io.File;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.JButton;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListModel;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import se.webbzon.boltzmann.editor.comparator.WorldObjectComparator;
import se.webbzon.boltzmann.editor.comparator.WorldObjectFactoryComparator;
import se.webbzon.boltzmann.editor.factory.worldobject.WorldObjectFactory;
import se.webbzon.boltzmann.editor.list.FactoryListCell;
import se.webbzon.boltzmann.editor.list.InstanceListCell;
import se.webbzon.boltzmann.editor.list.SortedListModel;
import se.webbzon.boltzmann.editor.mvc.EditorListener;
import se.webbzon.boltzmann.editor.mvc.EditorModel;
import se.webbzon.oschi01.worldarea.WorldArea;
import se.webbzon.oschi01.worldeditor.DeflatableWorldObject;
import se.webbzon.oschi01.worldeditor.WorldObjectCloner;
import se.webbzon.oschi01.worldeditor.WorldObjectClonerException;
import se.webbzon.oschi01.worldobject.WorldObject;

public class EditorContainer extends JPanel {
	
	// Auto generated serial version UID
	private static final long serialVersionUID = 5670506429726802043L;
	
	// The mvc-model of the editor
	private final EditorModel model;

	// The list of all available objects
	private final JList<WorldObjectFactory> factoryList;
	
	// The list of all instances
	private final JList<WorldObject> instanceList;
	
	/*============================================================
	Constructors
	============================================================*/
	
	public EditorContainer(EditorModel model) {
		this.model = model;
		
		final SortedListModel<WorldObjectFactory> factoryListModel = 
				new SortedListModel<WorldObjectFactory>(WorldObjectFactory.class,
														new WorldObjectFactoryComparator(),
														model.getWorldObjectFactories());
		
		final SortedListModel<WorldObject> instanceListModel = 
				new SortedListModel<WorldObject>(	WorldObject.class,
													new WorldObjectComparator());
		
		factoryList = new JList<WorldObjectFactory>(factoryListModel);
		instanceList = new JList<WorldObject>(instanceListModel);
		factoryList.setCellRenderer(new FactoryListCell());
		instanceList.setCellRenderer(new InstanceListCell());
		instanceList.setSelectionMode(ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
		instanceList.addKeyListener(new WorldViewKeyListener(model));
		
		setPreferredSize(new Dimension(256,480));
		setLayout(new BorderLayout());
		
		// Create the factory panel
		JPanel upperPanel = new JPanel();
		upperPanel.setLayout(new BorderLayout());
		upperPanel.setBorder(BorderFactory.createLoweredBevelBorder());
		upperPanel.add(new JLabel("World object factories"),BorderLayout.NORTH);
		JScrollPane factoryScrollPane = new JScrollPane(factoryList);
		factoryScrollPane.setPreferredSize(new Dimension(256,240));
		upperPanel.add(factoryScrollPane,BorderLayout.CENTER);
		// Create buttons
		JPanel upperButtonPanel = new JPanel();
		upperButtonPanel.setLayout(new BorderLayout());
		upperButtonPanel.add(new JButton(new AddNewAction()),BorderLayout.WEST);
		upperButtonPanel.add(new JButton(new ResortAction()),BorderLayout.EAST);
		upperPanel.add(upperButtonPanel,BorderLayout.SOUTH);
		add(upperPanel,BorderLayout.NORTH);
		
		// Create the instance panel
		JPanel lowerPanel = new JPanel();
		lowerPanel.setLayout(new BorderLayout());
		lowerPanel.setBorder(BorderFactory.createLoweredBevelBorder());
		lowerPanel.add(new JLabel("World objects"),BorderLayout.NORTH);
		JScrollPane instanceScrollPane = new JScrollPane(instanceList);
		instanceScrollPane.setPreferredSize(new Dimension(256,240));
		lowerPanel.add(instanceScrollPane,BorderLayout.CENTER);
		
		// Create buttons
		JPanel lowerButtonPanel = new JPanel();
		lowerButtonPanel.setLayout(new BorderLayout());
		lowerButtonPanel.add(new JButton(new RemoveAction()),BorderLayout.WEST);
		lowerButtonPanel.add(new JButton(new CopyAction()),BorderLayout.CENTER);
		lowerButtonPanel.add(new JButton(new OpenPropertyAction()),BorderLayout.EAST);
		lowerPanel.add(lowerButtonPanel,BorderLayout.SOUTH);
		add(lowerPanel,BorderLayout.CENTER);
		
		instanceList.addListSelectionListener(new ListSelectionListenerImpl());
		model.addListener(new EditorListenerImpl());
	}
	
	/*============================================================
	Public Methods
	============================================================*/
	
	/*============================================================
	Private Classes
	============================================================*/
	
	/** An implementation of an editor listener. **/
	private final class EditorListenerImpl implements EditorListener {

		/*============================================================
		Public Methods
		============================================================*/
		
		@Override public void onOpenWorld(Object source, WorldArea world) {
			for (WorldObject instance : world.findInstances(WorldObject.class)) 
				if (instance instanceof DeflatableWorldObject)
					((SortedListModel<WorldObject>) 
							instanceList.getModel()).addElement(instance);
			repaint();
		}
		
		@Override public void onCloseWorld(Object source, WorldArea world) {
			((SortedListModel<WorldObject>) instanceList.getModel()).clearElements();
			repaint();
		}

		@Override public void onAddInstance(Object source, WorldObject instance) {
			if (instance instanceof DeflatableWorldObject) {
				SortedListModel<WorldObject> model = 
						(SortedListModel<WorldObject>) instanceList.getModel();
				model.addElement(instance);
				repaint();
			}
		}

		@Override public void onRemoveInstance(Object source, WorldObject instance) {
			if (instance instanceof DeflatableWorldObject) {
				SortedListModel<WorldObject> model = 
						(SortedListModel<WorldObject>) instanceList.getModel();
				model.removeElement(instance);
				repaint();
			}
		}
		
		@Override public void onClearInstances(Object source) {
			SortedListModel<WorldObject> model = 
					(SortedListModel<WorldObject>) instanceList.getModel();
			model.clearElements();
			repaint();
		}

		@Override public void onSelectInstances(
				Object source, 
				Collection<WorldObject> selectedInstances,
				Collection<WorldObject> deselectedInstances) {
			if (source == EditorContainer.this)
				return;
			ArrayList<WorldObject> selections = new ArrayList<WorldObject>(selectedInstances);
			ListModel<WorldObject> model = instanceList.getModel();
			ListSelectionModel selectionModel = instanceList.getSelectionModel();
			instanceList.getSelectionModel().clearSelection();
			for (int i = 0; i < model.getSize(); i++) {
				inner : for (int j = 0; j < selections.size(); j++) {
					if (selections.get(j) == model.getElementAt(i)) {
						selectionModel.addSelectionInterval(i, i);
						selections.remove(j);
						break inner;
					}
				}
			}
			repaint();
		}

		@Override public void onSelectFile(Object source, File file) {};

		@Override public void onChangeInstance(Object source, WorldObject instance) {
			repaint();
		}

		@Override public void onFocusInstance(Object source, WorldObject instance) {}
		
	}
	
	/** An implementation on an abstract action used for the Add New button. **/
	private final class AddNewAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = -7256869394259450225L;
		
		/*============================================================
		Constructors
		============================================================*/

		public AddNewAction() {
			putValue(AbstractAction.NAME,"Add New");
		}
		
		/*============================================================
		Public Methods
		============================================================*/

		@Override public void actionPerformed(ActionEvent e) {
			final WorldObject instance = 
					factoryList.getSelectedValue().makeWorldObject();
			model.addInstance(this,instance);
			instanceList.setSelectedValue(instance, true);
		}
		
	}
	
	/** An implementation on an abstract action used for the Resort button. **/
	private final class ResortAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = -7954758075314025005L;
		
		/*============================================================
		Constructors
		============================================================*/

		public ResortAction() {
			putValue(AbstractAction.NAME,"Re-sort");
		}
		
		/*============================================================
		Public Methods
		============================================================*/

		@Override public void actionPerformed(ActionEvent e) {
			((SortedListModel<WorldObject>) instanceList.getModel()).resortElements();
			instanceList.clearSelection();
		}
		
	}
	
	/** An implementation on an abstract action used for the Open Property button. **/
	private final class OpenPropertyAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = -8516161752741432137L;
		
		/*============================================================
		Constructors
		============================================================*/
		
		public OpenPropertyAction() {
			putValue(Action.NAME,"Properties");
		}
		
		/*============================================================
		Public Methods
		============================================================*/

		@Override public void actionPerformed(ActionEvent event) {
			for (WorldObject instance : instanceList.getSelectedValuesList()) {
				if (instance instanceof DeflatableWorldObject) {
					final DeflatableWorldObject swo = (DeflatableWorldObject) instance;
					model.getPropertyFrameFactory(swo.getName()).makePropertyFrame(model, instance);
				}
			}
		}
		
	}
	
	/** An implementation on an abstract action used for the Remove button. **/
	private final class RemoveAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = 3595928228789092850L;
		
		/*============================================================
		Constructors
		============================================================*/
		
		public RemoveAction() {
			putValue(Action.NAME,"Remove");
		}
		
		/*============================================================
		Public Methods
		============================================================*/

		@Override public void actionPerformed(ActionEvent event) {
			List<WorldObject> instances = instanceList.getSelectedValuesList();
			if (!instances.isEmpty()) {
				final int choice = JOptionPane.showConfirmDialog(	EditorContainer.this, 
																	"Remove " + 
																	instances.size() + 
																	" instance(s) from this world?", 
																	"Confirm", 
																	JOptionPane.YES_NO_OPTION, 
																	JOptionPane.WARNING_MESSAGE);
				if (choice == JOptionPane.YES_OPTION)
					for (WorldObject instance : instances)
						model.removeInstance(EditorContainer.this,instance);
			}
		}
		
	}
	
	/** An implementation on an abstract action used for the Copy button. **/
	private final class CopyAction extends AbstractAction {
		
		// Auto generated serial version UID
		private static final long serialVersionUID = 6662071979279881667L;
		
		/*============================================================
		Constructors
		============================================================*/
		
		public CopyAction() {
			putValue(Action.NAME,"Copy");
		}
		
		/*============================================================
		Public Methods
		============================================================*/

		@Override public void actionPerformed(ActionEvent event) {
			List<WorldObject> worldObjects = instanceList.getSelectedValuesList();
			ArrayList<DeflatableWorldObject> dwo = new ArrayList<DeflatableWorldObject>();
			for (WorldObject worldObject : worldObjects) 
				if (worldObject instanceof DeflatableWorldObject)
					dwo.add((DeflatableWorldObject) worldObject);
			
			WorldObjectCloner cloner = new WorldObjectCloner(model.getInflatorGroup());
			try {
				Collection<WorldObject> clones = cloner.clone(dwo);
				for (WorldObject clone : clones)
					model.addInstance(this,clone);
				model.selectInstances(this,clones);
			} catch (WorldObjectClonerException e) {
				e.printStackTrace();
			}
			
		}
		
	}
	
	/** An implementation of a list selection listener. **/
	private final class ListSelectionListenerImpl implements ListSelectionListener {

		@Override public void valueChanged(ListSelectionEvent event) {
			model.selectInstances(EditorContainer.this,instanceList.getSelectedValuesList());
		}
		
	}

}